//
//  AssetPersistenceManager.swift
//  MyVideo
//
//  Created by han on 2021/4/22.
//

import UIKit
import AVFoundation

// 下载:AVAssetDownloadURLSession AVAggregateAssetDownloadTask
class AssetPersistenceManager: NSObject {
    static let shared = AssetPersistenceManager()
    
    /// 恢复
    private var didRestorePersistenceManager = false
    
    private var assetDownloadURLSession: AVAssetDownloadURLSession! // 下载session
    private var activeDownloadsMap = [AVAggregateAssetDownloadTask: Asset]() // 当前下载中的task,asset
    private var willDownloadURLMap = [AVAggregateAssetDownloadTask: URL]()
    
    private override init() {
        super.init()
        let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: "AAPL-Identifier")
        assetDownloadURLSession = AVAssetDownloadURLSession(configuration: backgroundConfiguration, assetDownloadDelegate: self, delegateQueue: OperationQueue.main)
    }
    
    
}

extension AssetPersistenceManager {
    // 开启下载任务
    func restorePersistenceManager() {
        guard !didRestorePersistenceManager else { return }
        
        assetDownloadURLSession.getAllTasks { (tasks) in
            for task in tasks {
                guard let assetDownloadTask  = task as? AVAggregateAssetDownloadTask, let assetName = task.taskDescription else { break }
                let stream = StreamListManager.shared.stream(with: assetName)
                let urlAsset = assetDownloadTask.urlAsset
                let asset = Asset(stream: stream, urlAsset: urlAsset)
                self.activeDownloadsMap[assetDownloadTask] = asset
            }
            NotificationCenter.default.post(name: .AssetPersistenceManagerDidRestoreState, object: nil)
        }
    }
    
    func downloadStream(for asset: Asset) {
        let preferredMediaSelection = asset.urlAsset.preferredMediaSelection
        guard let task = assetDownloadURLSession.aggregateAssetDownloadTask(with: asset.urlAsset, mediaSelections: [preferredMediaSelection], assetTitle: asset.stream.name, assetArtworkData: nil, options: [AVAssetDownloadTaskMinimumRequiredMediaBitrateKey: 265_000]) else { return }
        task.taskDescription = asset.stream.name
        activeDownloadsMap[task] = asset
        task.resume()
        
        var userInfo = [String: Any]()
        userInfo[Asset.Keys.name] = asset.stream.name
        userInfo[Asset.Keys.downloadState] = Asset.DownloadState.downloading.rawValue
        userInfo[Asset.Keys.downloadSelectionDisplayName] = displayNamesForSelectedMediaOptions(preferredMediaSelection)
        NotificationCenter.default.post(name: .AssetDownloadStateChanged, object: nil, userInfo: userInfo)
    }
    
    func deleteAsset(_ asset: Asset) {
        let userDefaults = UserDefaults.standard
        do {
            if let localFileLocation = localAssetForStream(with: asset.stream.name)?.urlAsset.url {
                try FileManager.default.removeItem(at: localFileLocation)
                userDefaults.removeObject(forKey: asset.stream.name)
            }
            var userInfo = [String: Any]()
            userInfo[Asset.Keys.name] = asset.stream.name
            userInfo[Asset.Keys.downloadState] = Asset.DownloadState.notDownloaded.rawValue
            NotificationCenter.default.post(name: .AssetDownloadStateChanged, object: nil,
                                            userInfo: userInfo)
        } catch  {
            print("An error occured deleting the file: \(error)")
        }
    }
    
    func cancleDownload(_ asset: Asset) {
        var task: AVAggregateAssetDownloadTask?
        for (downloadTask, assetValue) in activeDownloadsMap where assetValue == asset {
            task = downloadTask
            break
        }
        task?.cancel()
    }
    
    func assetForStream(withName name: String) -> Asset? {
        var asset: Asset?

        for (_, assetValue) in activeDownloadsMap where name == assetValue.stream.name {
            asset = assetValue
            break
        }

        return asset
    }
    
    func localAssetForStream(with name: String) -> Asset? {
        guard let localFileLocation = UserDefaults.standard.value(forKey: name) as? Data else { return nil }
        var asset: Asset?
        var bookmarkDataIsStale = false
        do {
            // 通过bookMark保存和获取路径的url
            let url = try URL(resolvingBookmarkData: localFileLocation, bookmarkDataIsStale: &bookmarkDataIsStale)
            if bookmarkDataIsStale {
                fatalError("Bookmark data is stale!")
            }
            let urlAsset = AVURLAsset(url: url)
            let stream = StreamListManager.shared.stream(with: name)
            asset = Asset(stream: stream, urlAsset: urlAsset)
            return asset
        } catch  {
            fatalError("Failed to create URL from bookmark with error: \(error)")
        }
    }
    
    func downloadState(for asset: Asset) -> Asset.DownloadState {
        if let localFileLocation = localAssetForStream(with: asset.stream.name)?.urlAsset.url {
            if FileManager.default.fileExists(atPath: localFileLocation.path) {
                return .downloaded
            }
        }
        for (_, assetValue) in activeDownloadsMap where assetValue.stream.name == asset.stream.name {
            return .downloading
        }
        return .notDownloaded
    }
    
    func displayNamesForSelectedMediaOptions(_ mediaSelection: AVMediaSelection) -> String {
        var displayNames = ""
        guard let asset = mediaSelection.asset else {
            return displayNames
        }
        
        for mediaCharacteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions {
            guard let mediaSelectionGroup =
                asset.mediaSelectionGroup(forMediaCharacteristic: mediaCharacteristic),
                let option = mediaSelection.selectedMediaOption(in: mediaSelectionGroup) else { continue }
            if displayNames.isEmpty {
                displayNames += " " + option.displayName
            } else {
                displayNames += ", " + option.displayName
            }
        }
        return displayNames
    }
}

extension AssetPersistenceManager: AVAssetDownloadDelegate {
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        // 下载完成
        guard let task = task as? AVAggregateAssetDownloadTask, let asset = activeDownloadsMap.removeValue(forKey: task), let downloadURL = willDownloadURLMap.removeValue(forKey: task) else { return }
       
        var userInfo = [String: Any]()
        userInfo[Asset.Keys.name] = asset.stream.name
        
        if let error = error as NSError? { // 下载失败
            switch (error.domain, error.code) {
            case (NSURLErrorDomain, NSURLErrorCancelled):
                guard let localFileLocation = localAssetForStream(with: asset.stream.name)?.urlAsset.url else { return }
                do {
                    try FileManager.default.removeItem(at: localFileLocation)
                    UserDefaults.standard.removeObject(forKey: asset.stream.name)
                } catch {
                    print("An error occured trying to delete the contents on disk for \(asset.stream.name): \(error)")
                }
                userInfo[Asset.Keys.downloadState] = Asset.DownloadState.notDownloaded.rawValue
                
            case (NSURLErrorDomain, NSURLErrorUnknown):
                fatalError("Downloading HLS streams is not supported in the simulator.")
            default:
                fatalError("An unexpected error occured \(error.domain)")
            }
        } else { // 下载成功
            do {
                let bookmark = try downloadURL.bookmarkData()
                UserDefaults.standard.set(bookmark, forKey: asset.stream.name)
            } catch  {
                print("Failed to create bookmarkData for download URL.")
            }
            userInfo[Asset.Keys.downloadState] = Asset.DownloadState.downloaded.rawValue
            userInfo[Asset.Keys.downloadSelectionDisplayName] = ""
        }
        
        NotificationCenter.default.post(name: .AssetDownloadStateChanged, object: nil, userInfo: userInfo)
    }
    
    func urlSession(_ session: URLSession, aggregateAssetDownloadTask: AVAggregateAssetDownloadTask, willDownloadTo location: URL) {
        willDownloadURLMap[aggregateAssetDownloadTask] = location
    }
    
    func urlSession(_ session: URLSession, aggregateAssetDownloadTask: AVAggregateAssetDownloadTask, didCompleteFor mediaSelection: AVMediaSelection) {
        guard let asset = activeDownloadsMap[aggregateAssetDownloadTask] else { return }
       
        var userInfo = [String: Any]()
        userInfo[Asset.Keys.name] = asset.stream.name
        aggregateAssetDownloadTask.taskDescription = asset.stream.name
        aggregateAssetDownloadTask.resume()
        userInfo[Asset.Keys.downloadState] = Asset.DownloadState.downloading.rawValue
        userInfo[Asset.Keys.downloadSelectionDisplayName] = displayNamesForSelectedMediaOptions(mediaSelection)
        
        NotificationCenter.default.post(name: .AssetDownloadStateChanged, object: nil, userInfo: userInfo)
    }
    
    func urlSession(_ session: URLSession, aggregateAssetDownloadTask: AVAggregateAssetDownloadTask, didLoad timeRange: CMTimeRange, totalTimeRangesLoaded loadedTimeRanges: [NSValue], timeRangeExpectedToLoad: CMTimeRange, for mediaSelection: AVMediaSelection) {
        guard let asset = activeDownloadsMap[aggregateAssetDownloadTask] else { return }
        
        var percentComplete = 0.0
        for value in loadedTimeRanges {
            let loadedTimeRange: CMTimeRange = value.timeRangeValue
            percentComplete +=
                loadedTimeRange.duration.seconds / timeRangeExpectedToLoad.duration.seconds
        }
        
        var userInfo = [String: Any]()
        userInfo[Asset.Keys.name] = asset.stream.name
        userInfo[Asset.Keys.percentDownloaded] = percentComplete

        NotificationCenter.default.post(name: .AssetDownloadProgress, object: nil, userInfo: userInfo)
    }
}

extension Notification.Name {
    static let AssetDownloadProgress = Notification.Name(rawValue: "AssetDownloadProgressNotification")
    static let AssetDownloadStateChanged = Notification.Name(rawValue: "AssetDownloadStateChangedNotification")
    static let AssetPersistenceManagerDidRestoreState = Notification.Name("AssetPersistenceManagerDidRestoreStateNotification")
}
